package main;

import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
//import java.awt.image.BufferedImage;
//import java.io.ByteArrayInputStream;
//import java.io.ByteArrayOutputStream;
//import java.io.IOException;
import java.util.ArrayList;

//import javax.imageio.ImageIO;

/*
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
*/

public class Utils {
	
	public static int pixelsPerInch = 30;
	
	public enum spellType { UNTARGETED, TARGETED, AOE, UNKNOWN };
	
	public static int[][] woundChart = {{4,5,5,6,6,7,7,7,7,7},
			{4,4,5,5,6,6,7,7,7,7},
			{3,4,4,5,5,6,6,7,7,7},
			{3,3,4,4,5,5,6,6,7,7},
			{3,3,3,4,4,5,5,6,6,7},
			{3,3,3,3,4,4,5,5,6,6},
			{3,3,3,3,3,4,4,5,5,6},
			{3,3,3,3,3,3,4,4,5,5},
			{3,3,3,3,3,3,3,4,4,5},
			{3,3,3,3,3,3,3,3,4,4}};
	
	public static double inchDist(int x1, int y1, int x2, int y2) {
		return Math.pow(Math.pow(x1-x2, 2) + Math.pow(y1-y2, 2), 0.5)/pixelsPerInch;
	}
	
	public static double inchDist(Unit u1, Unit u2) {
		return Math.pow(Math.pow(u1.x-u2.x, 2) + Math.pow(u1.y-u2.y, 2), 0.5)/pixelsPerInch;
	}
	
	public static double pixelDist(Unit u1, Unit u2) {
		return Math.pow(Math.pow(u1.x-u2.x, 2) + Math.pow(u1.y-u2.y, 2), 0.5);
	}
	
	public static double pixelDist(int x1, int y1, int x2, int y2) {
		return Math.pow(Math.pow(x1-x2, 2) + Math.pow(y1-y2, 2), 0.5);
	}
	
	public static double pixelDist(double x1, double y1, double x2, double y2) {
		return Math.pow(Math.pow(x1-x2, 2) + Math.pow(y1-y2, 2), 0.5);
	}
	
	public static Unit getFromListByString(ArrayList<Unit> list, String unitName) {
		for (Unit u : list) {
			if (u.toChoiceString().equals(unitName)) 
				return u;
		}
		return null;
	}
	
	public static boolean wound(int roll, int strength, int defense) {
		if (woundChart[strength-1][defense-1] <= roll)
			return true;
		return false;
	}
	
	public static int neededToWound(int strength, int defense) {
		return woundChart[strength-1][defense-1];
	}
	
	public static spellType getSpellType(String spellName) {
		if (spellName.equals("Aura of Command") ||
				spellName.equals("Aura of Dismay") ||
				spellName.equals("Terrifying Aura") ||
				spellName.equals("Nature's Wrath"))
			return spellType.UNTARGETED;
		else if (spellName.equals("Black Dart") ||
				spellName.equals("Chill Soul") ||
				spellName.equals("Command") ||
				spellName.equals("Compel") ||
				spellName.equals("Drain Courage") || 
				spellName.equals("Immobilise") ||
				spellName.equals("Transfix") ||
				spellName.equals("Renew") ||
				spellName.equals("Sap Will") || 
				spellName.equals("Sorcerous Blast") ||
				spellName.equals("Strengthen Will"))			
			return spellType.TARGETED;
		else if (spellName.equals("Blinding Light"))
			return spellType.AOE;
		else 
			return spellType.UNKNOWN;
	}
	
	public static boolean areaIsConnected(Area a) {
		PathIterator pi = a.getPathIterator(null);
		double[] coords = {0,0,0,0,0,0};
		
		int moveType = pi.currentSegment(coords);
		ArrayList<Polygon> polyList = new ArrayList<Polygon>();
		Polygon currPoly = new Polygon();
		
		while(!pi.isDone()) {
			switch (moveType) {
			case PathIterator.SEG_MOVETO:
				currPoly = new Polygon();
				currPoly.addPoint((int)coords[0], (int)coords[1]);
				break;
			case PathIterator.SEG_LINETO:
				currPoly.addPoint((int)coords[0], (int)coords[1]);
				break;
			case PathIterator.SEG_CLOSE:
				polyList.add(currPoly);
				break;
			default:
                System.out.println("PathIterator contains curved segments");
                return false;
			}
			pi.next();
			if (!pi.isDone())
				moveType = pi.currentSegment(coords);
		}
		// If only one polygon, it's not disconnected
		if (polyList.size() <= 1) {
			return true;
		}
		// If there's multiple polygons, we can check by finding the biggest one
		// and seeing if all the points in all other polygons are contained inside
		// (In general we don't have layered areas, and even if we do we don't want
		// to identify them as separate)
		Rectangle largestBounds = polyList.get(0).getBounds();
		int largestIndex = 0;
		for (int i = 1; i < polyList.size(); i++) {
			Rectangle candRect = polyList.get(i).getBounds();
			if (candRect.width*candRect.height > largestBounds.width*largestBounds.height){
				largestBounds = candRect;
				largestIndex = i;
			}
		}
		Polygon largestPoly = polyList.get(largestIndex);
		for (int i = 0; i < polyList.size(); i++) {
			if (i == largestIndex)
				continue;
			int npoints = polyList.get(i).npoints;
			int[] xs =  polyList.get(i).xpoints;
			int[] ys = polyList.get(i).ypoints;
			for (int j = 0; j < npoints; j++) {
				if (!largestPoly.contains(xs[j],ys[j])) {
					return false;
				}
			}
		}
		System.out.println(polyList.size());
		
		return true;
	}
	
	public static Point2D findContactPoint(Unit mover, Unit blocker, int destX, int destY) {
		double d = Utils.pixelDist(mover.x, mover.y, blocker.x, blocker.y);
		double theta = Math.abs(Math.atan2(blocker.y-mover.y, blocker.x-mover.x) -
						Math.atan2(destY-mover.y, destX-mover.x));   // Absolute angle between path line and line between units
		if (theta > Math.PI/2)
			return null;    // If we're moving away from the blocker, we won't run into him
				
		double ratio = Math.sin(theta)/(mover.pixelRadius+blocker.pixelRadius); // Ratio from the law of sines, sin(theta) over the opposite side of the triangle
		double beta = Math.asin(ratio*d);  // Angle created by root-contactPoint-blocker (closer point)
		double beta2 = Math.PI - beta;     // Angle created by root-contactPoint-blocker (further point)
		
		double gamma = Math.PI - (theta + beta);    // Angle created by root-blocker-contactPoint (closer point)
		double gamma2 = Math.PI - (theta + beta2);  // Angle created by root-blocker-contactPoint (further point)
		double x1 = (Math.sin(gamma)/ratio)/Utils.pixelsPerInch;     // inch distance to close intersect point 
		double x2 = (Math.sin(gamma2)/ratio)/Utils.pixelsPerInch;    // inch distance to far intersect point
		
		double x = Math.min(x1, x2);  // distance to the closest intersect point
		
		if (x < Utils.inchDist(mover.x, mover.y, destX, destY)) {
			int newX = (int)(mover.x + x*Utils.pixelsPerInch*(destX-mover.x)/Utils.pixelDist(mover.x, mover.y, destX, destY));
			int newY = (int)(mover.y + x*Utils.pixelsPerInch*(destY-mover.y)/Utils.pixelDist(mover.x, mover.y, destX, destY));
			return new Point2D.Double(newX, newY);
		}
		else
			return null;
			
		
	}
	
	public static Point2D findLineIntersect(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) {
		double denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
		  if (denom == 0.0) { // Lines are parallel.
		     return null;
		  }
		  double ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3))/denom;
		  return new Point2D.Double((x1 + ua*(x2 - x1)), (y1 + ua*(y2 - y1)));
	}
	
	public static boolean segmentIntersect(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) {
		double slope1 = 0;
		double b1 = 0;
		double slope2 = 0;
		double b2 = 0;
		
		double intersectX = 0;
		double intersectY = 0;
		
		// Check for vertical lines
		if(x1 == x2 && x3 == x4) {
			return (x1 == x3 && ((y1 >= Math.min(y3, y4) && y1 <= Math.max(y3, y4)) || 
								(y2 >= Math.min(y3, y4) && y2 <= Math.max(y3, y4))));  // On the same line and segments overlap
		}
		else if (x1 == x2) {
			slope2 = (y4-y3)/(x4-x3);
			b2 = y3 - slope2*x3;
			
			intersectY = slope2*x1 + b2;
			return (intersectY >= Math.min(y1, y2) && intersectY <= Math.max(y1, y2));
		}
		else if (x3 == x4) {
			slope1 = (y2-y1)/(x2-x1);
			b1 = y1 - slope1*x1;
			
			intersectY = slope1*x3 + b1;
			return (intersectY >= Math.min(y3, y4) && intersectY <= Math.max(y3, y4));
		}
		
		// No vertical, calculate slopes and intersects
		slope1 = (y2-y1)/(x2-x1);
		b1 = y1 - slope1*x1;
		slope2 = (y4-y3)/(x4-x3);
		b2 = y3 - slope2*x3;
		
		// Check for parallel lines
		if (slope1 == slope2)
			return (x1 == x3 && ((y1 >= Math.min(y3, y4) && y1 <= Math.max(y3, y4)) || 
								(y2 >= Math.min(y3, y4) && y2 <= Math.max(y3, y4))));
		
		// Otherwise, get intersect point and check if it's on both lines
		intersectX = -(b1-b2)/(slope1-slope2);
		return (intersectX >= Math.min(x1, x2) && intersectX <= Math.max(x1, x2)) &&
				(intersectX >= Math.min(x3, x4) && intersectX <= Math.max(x3, x4));
	}
	
	
	public static double distanceFromLine(double pointX, double pointY, double ax, double ay ,
			  double bx, double by, boolean isLine) {
		// find the distance from the point (pointX,pointY) to the line
		// determined by the points (ax,ay) and (bx,by)
		//
		// isLine - Whether the line shoud be extended beyond a and b or is a segment.
		//
		
		/*
		
		Subject 1.02: How do I find the distance from a point to a line?
		
		
		Let the point be C (Cx,Cy) and the line be AB (Ax,Ay) to (Bx,By).
		Let P be the point of perpendicular projection of C on AB.  The parameter
		r, which indicates P's position along AB, is computed by the dot product 
		of AC and AB divided by the square of the length of AB:
		
		(1)     AC dot AB
		r = ---------  
		  ||AB||^2
		
		r has the following meaning:
		
		r=0      P = A
		r=1      P = B
		r<0      P is on the backward extension of AB
		r>1      P is on the forward extension of AB
		0<r<1    P is interior to AB
		
		The length of a line segment in d dimensions, AB is computed by:
		
		L = sqrt( (Bx-Ax)^2 + (By-Ay)^2 + ... + (Bd-Ad)^2)
		
		so in 2D:   
		
		L = sqrt( (Bx-Ax)^2 + (By-Ay)^2 )
		
		and the dot product of two vectors in d dimensions, U dot V is computed:
		
		D = (Ux * Vx) + (Uy * Vy) + ... + (Ud * Vd)
		
		so in 2D:   
		
		D = (Ux * Vx) + (Uy * Vy) 
		
		So (1) expands to:
		
		  (Cx-Ax)(Bx-Ax) + (Cy-Ay)(By-Ay)
		r = -------------------------------
		                L^2
		
		The point P can then be found:
		
		Px = Ax + r(Bx-Ax)
		Py = Ay + r(By-Ay)
		
		And the distance from A to P = r*L.
		
		Use another parameter s to indicate the location along PC, with the 
		following meaning:
		 s<0      C is left of AB
		 s>0      C is right of AB
		 s=0      C is on AB
		
		Compute s as follows:
		
		  (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay)
		s = -----------------------------
		              L^2
		
		
		Then the distance from C to P = |s|*L.
		
		*/
		
		
		double r_numerator = (pointX-ax)*(bx-ax) + (pointY-ay)*(by-ay);
		double r_denomenator = (bx-ax)*(bx-ax) + (by-ay)*(by-ay);
		double r = r_numerator / r_denomenator;
		//
		double s =  ((ay-pointY)*(bx-ax)-(ax-pointX)*(by-ay) ) / r_denomenator;
		
		if (isLine)
			return Math.abs(s)*Math.sqrt(r_denomenator);
		
		//
		//(xx,yy) is the point on the lineSegment closest to (cx,cy)
		//
		
		if ( (r >= 0) && (r <= 1) ) {
			return Math.abs(s)*Math.sqrt(r_denomenator);
		}
		else {
			double dist1 = (pointX-ax)*(pointX-ax) + (pointY-ay)*(pointY-ay);
			double dist2 = (pointX-bx)*(pointX-bx) + (pointY-by)*(pointY-by);
			if (dist1 < dist2) {
				return Math.sqrt(dist1);
			}
			else {
				return Math.sqrt(dist2);
			}
		}
	}
	
	/**
     * Decode string to image
     * @param imageString The string to decode
     * @return decoded image
     */
    /*public static BufferedImage decodeToImage(String imageString) {

        BufferedImage image = null;
        byte[] imageByte;
        try {
            BASE64Decoder decoder = new BASE64Decoder();
            imageByte = decoder.decodeBuffer(imageString);
            ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
            image = ImageIO.read(bis);
            bis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return image;
    }
    
    public static BufferedImage decodeToImage(String[] imageStrings) {
    	String imageString = "";
    	for (String s : imageStrings)
    		imageString += s;
        BufferedImage image = null;
        byte[] imageByte;
        try {
            BASE64Decoder decoder = new BASE64Decoder();
            imageByte = decoder.decodeBuffer(imageString);
            ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
            image = ImageIO.read(bis);
            bis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return image;
    }

    /**
     * Encode image to string
     * @param image The image to encode
     * @param type jpeg, bmp, ...
     * @return encoded string
     */
    /*public static String encodeToString(BufferedImage image, String type) {
        String imageString = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        try {
            ImageIO.write(image, type, bos);
            byte[] imageBytes = bos.toByteArray();

            BASE64Encoder encoder = new BASE64Encoder();
            imageString = encoder.encode(imageBytes);

            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return imageString;
    }

    /*public static void main (String args[]) throws IOException {
        BufferedImage img = ImageIO.read(new File("files/img/TestImage.png"));
        BufferedImage newImg;
        String imgstr;
        imgstr = encodeToString(img, "png");
        System.out.println(imgstr);
        newImg = decodeToImage(imgstr);
        ImageIO.write(newImg, "png", new File("files/img/CopyOfTestImage.png"));
    }*/
}